package com.easylinkin.linkappapi.security.config;

import cn.hutool.core.util.StrUtil;
import com.easylinkin.linkappapi.security.service.LinkappUserLoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.session.InvalidSessionStrategy;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;
import org.springframework.web.filter.CharacterEncodingFilter;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

/**
 * 安全认证配置
 *
 * @author liuming
 */
@Configuration
@EnableWebSecurity
@Order(1)
public class LinkappSecurityConfiguration extends WebSecurityConfigurerAdapter {

  @Resource
  LinkappUserLoginService linkappUserLoginService;

  @Resource
  private SessionInformationExpiredStrategy sessionInformationExpiredStrategy;

  @Resource
  private InvalidSessionStrategy invalidSessionStrategy;

  /**
   * 注册用户认证服务
   */
  @Override
  public LinkappUserLoginService userDetailsService() {
    return linkappUserLoginService;
  }

  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(linkappUserLoginService);
    auth.authenticationProvider(new SsoLoginAuthenticationProvider(linkappUserLoginService));
  }

  /*************************	解决@Async异步方法调用Spring Security认证传递问题 获取不到SecurityContextHolder.getContext().getAuthentication();导致getCurrentUsername为null	**************************************/
  @Bean
  public AppSpringSecurityStrategy appSpringSecurityStrategy() {
    AppSpringSecurityStrategy securityStrategy = new AppSpringSecurityStrategy();
    return securityStrategy;
  }

  private class AppSpringSecurityStrategy {

    @PostConstruct
    public void init() {
      SecurityContextHolder.setStrategyName(
          SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
    }
  }

  /*************************	END		**************************************/

  @Bean
  LinkappUsernamePasswordAuthenticationFilter linkappUsernamePasswordAuthenticationFilter()
      throws Exception {
    LinkappUsernamePasswordAuthenticationFilter filter = new LinkappUsernamePasswordAuthenticationFilter();
    filter.setAuthenticationManager(super.authenticationManagerBean());
    filter.setAuthenticationSuccessHandler(linkappRestAuthenticationSuccessHandler);
    filter.setAuthenticationFailureHandler(linkappRestAuthenticationFailureHandler);
    return filter;
  }

  @Bean
  LinkappPhoneLoginAuthenticationFilter linkappPhoneLoginAuthenticationFilter() throws Exception {
    LinkappPhoneLoginAuthenticationFilter filter = new LinkappPhoneLoginAuthenticationFilter();
    filter.setAuthenticationManager(super.authenticationManagerBean());
    filter.setAuthenticationSuccessHandler(linkappRestAuthenticationSuccessHandler);
    filter.setAuthenticationFailureHandler(linkappRestAuthenticationFailureHandler);
    return filter;
  }

  @Bean
  LinkappMiniAppLoginAuthenticationFilter linkappMiniAppLoginAuthenticationFilter()
      throws Exception {
    LinkappMiniAppLoginAuthenticationFilter filter = new LinkappMiniAppLoginAuthenticationFilter();
    filter.setAuthenticationManager(super.authenticationManagerBean());
    filter.setAuthenticationSuccessHandler(linkappMiniAppAuthenticationSuccessHandler);
    filter.setAuthenticationFailureHandler(linkappMiniAppAuthenticationFailureHandler);
    return filter;
  }

  /**
   * 定制一些全局性的安全配置，一般配置一些不拦截静态资源的访问
   *
   * @param webSecurity
   * @throws Exception
   */
  @Override
  public void configure(WebSecurity webSecurity) throws Exception {
    webSecurity
        .ignoring()
        .requestMatchers(request -> (
            requestFilter(request)
        ));
  }

  private boolean requestFilter(HttpServletRequest request) {
    //只校验带token
    boolean result = StrUtil.isNotEmpty(request.getHeader("token"));

    //校验路径和token
//		boolean result = request.getRequestURI().equals("/device/getMonitorPage") && StrUtil.isNotEmpty(request.getHeader("token"));
    return result;
  }

  @Autowired
  private LinkappMiniAppAuthenticationSuccessHandler linkappMiniAppAuthenticationSuccessHandler;

  @Autowired
  private LinkappMiniAppAuthenticationFailureHandler linkappMiniAppAuthenticationFailureHandler;

  @Autowired
  private LinkappRestAuthenticationSuccessHandler linkappRestAuthenticationSuccessHandler;

  @Autowired
  private LinkappRestAuthenticationFailureHandler linkappRestAuthenticationFailureHandler;

  @Autowired
  private LinkappRestLogoutSuccessHandler linkappRestLogoutSuccessHandler;

  @Autowired
  AuthenticationTokenFilter authenticationTokenFilter;

  @Autowired
  RequestFilter requestFilter;

  @Override
  protected void configure(HttpSecurity http) throws Exception {

//		解决 spring security 对于开放接口返回乱码的解决
    CharacterEncodingFilter filter = new CharacterEncodingFilter();
    filter.setEncoding("UTF-8");
    filter.setForceEncoding(true);
    http.addFilterBefore(filter, CsrfFilter.class);
    http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    http.addFilterBefore(requestFilter, BasicAuthenticationFilter.class);
    http
        // 禁止匿名用户
        // .anonymous().disable()
        // 禁止csrfz

        .csrf().disable()
        // 认证失败处理
        .exceptionHandling()
        .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
        .and()
        // 白名单
        .authorizeRequests()
        .antMatchers(
                // "/swagger-ui.html",  "/swagger-resources/**", "/v2/api-docs/**",
                "/webjars/**",
            "/user/verificationCode", "/user/sendVerificationCode", "/user/verificationMessages",
            "/user/resetPassword", "/adminUser/verificationCode",
            "/adminUser/isEnableVerificationCode", "/linkappUser/sendVerificationCode",
            "/linkappUser/verificationMessages", "/linkappUser/resetPassword",
            "/program/getProgramInfo/*", "/lang", "/checkhealth", "/datapush", "/alarmListen/**",
            "/airDeviceMonitorListener/**", "/deviceAttributeStatusListener/**",
            "/deviceMonitorListener/**",
            "/synchronizationLinkthing", "/ssoLogin", "/tenant/getSSOUrl",
            "/tenant/addLinkappTenant", "/adminlogin", "/application/getPersonalIfo",
            "/adminPhoneLogin", "/meterLogin", "/meterCodeLogin", "/recharge/thirdAsynNotify",
            "/resident/verificationCode", "/resident/checkVerificationCode",
            "/resident/updatePwdByForget",
            "/personality/getPersonalIfo", "/onlineStateChange", "/anon/**", "/miniappLogin",
            "/linkappUser/verificationCode/**", "/phoneLogin", "/applicationCopy",
            "/selectAllPrivilegeByApplication", "/selectPrivilegeByApplication",
            "/selectPersonalityByApplication", "/initLinkappTenant",
            "/updatePrivilegePersonalityByApplication",
            "/getTenantInfo", "/updateTenantAccount", "/lock", "/unlock",
            "/synchronizeDeviceUnitAndDevicesByProjectNoLogin", "/openapi/**",
            "/synchronizationDeviceUnit", "/synchronizationDeleteDeviceUnit", "/personnel/**",
            "/dahc/webcamJumpPage", "/appversion/**", "/appElectricBox/detail",
            "/linkappUser/resetPasswordByVerificationCode", "/enterpriseEditionBi/**","/oss/download/**",
            "/download/**","/config/getEduUrl","/cameraopenApi/**", "/**/openapi/**",
            "/enterpriseOpenApi/**","/enterpriseUser/**","/appversion/appVersionInfo/queryAppLatestVersion",
            "/enterpriseMachinery/**","/lobar/visitorRecord/add","/userLogin/userCheck","/deviceAttributeStatus/getDeviceDataByProject",
                "/deviceData/**")
        .permitAll()
        // 接口调试阶段 目前不校验接口 modify by tongjie
        .anyRequest().authenticated().and()
        // 表单登录配置
        .formLogin()
        // 登录成功处理
        .successHandler(linkappRestAuthenticationSuccessHandler)
        // 登录失败处理
        .failureHandler(linkappRestAuthenticationFailureHandler).and()
        // 登出成功处理
        .logout().logoutSuccessHandler(linkappRestLogoutSuccessHandler);
      //一个账号只允许一个地方登录,登录超时、踢人下线都实现了，就差消息通知了，没有说加，先让允许10个地方同时登录吧
      http.sessionManagement()
              //session失效，调用此方法
              .invalidSessionStrategy(invalidSessionStrategy).maximumSessions(10)
              // 当用户达到最大session数后，则调用此处的实现
              .expiredSessionStrategy(sessionInformationExpiredStrategy);
  }

    @Bean
    @ConditionalOnMissingBean(InvalidSessionStrategy.class)
    public InvalidSessionStrategy invalidSessionStrategy() {
        return new MyInvalidSessionStrategy();
    }
    @Bean
    @ConditionalOnMissingBean(SessionInformationExpiredStrategy.class)
    public SessionInformationExpiredStrategy informationExpiredStrategy() {
        return new MySessionInformationExpiredStrategy();
    }
}
